#pragma once

#include <map>
#include "keys.h"
#include "namedKey.h"
#include "..\EdgeGameCore\Delegate.h"
#include "..\EdgeMath\Vectors.h"
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>

using namespace std;

typedef void(*key_event_handler)(void**);

typedef void(*mouse_event_handler)(void**);

class Input
{
	struct MouseData
	{
		float delta_x;
		float delta_y;
	} mouseData;

	// buffer of currently pressed keys
	short curr_buffer[256];
	// buffer of previously pressed keys
	short prev_buffer[256];

	// the x position of the mouse
	int mouse_x = 0;
	// the y position of the mouse
	int mouse_y = 0;

	// value that indicates the mouse move event should be ignored for one frame
	bool ignoreMouseMove = false;

	IDirectInputDevice8* pMouse = nullptr;
	LPDIRECTINPUT8 direct_input = nullptr;
	
	// event-based input
	map<NamedKey, Delegate<key_event_handler>*> key_bindings;
	map<NamedKey, Delegate<mouse_event_handler>*> mouse_bindings;

	// private helper functions
	bool isKeyDown(int keyCode);
	bool isKeyUp(int keyCode);

	// singleton instance
	static Input* instance;
	
	// window handle
	HWND hwnd;

	// indicates if the window is currently focoused
	bool windowFocoused = false;

	// private ctor and dtor
	Input(void);
	~Input(void);
public:

	// event raised when the mouse is moved
	Delegate<key_event_handler>* mouse_moved;
	// event raised when the scroll weel moves up
	Delegate<key_event_handler>* scrollWeel_up;
	// event raised when the scroll weel moves down
	Delegate<key_event_handler>* scrollWeel_down;

	void WinProc(UINT message, WPARAM wParam, LPARAM lParam);

	// returns a boolean indicateing 'keyCode' is currently held down by
	// the user
	bool isKeyDown(Keys keyCode);
	// returns a boolean indicateing 'button' is currently held down by
	// the user
	bool isKeyDown(MouseButtons button);

	// returns a boolean indicating 'keyCode' is not currently held down 
	// by the user
	bool isKeyUp(Keys keyCode);
	// returns a boolean indicating 'button' is not currently held down 
	// by the user
	bool isKeyUp(MouseButtons button);

	// indicates if 'keyCode' has changed from up to down in the current cycle
	bool isKeyPressed(Keys keyCode);
	// indicates if 'button' has changed from up to down in the current cycle
	bool isKeyPressed(MouseButtons button);

	// indicates that 'keycode' was held down in the previous frame
	bool wasKeyDown(Keys keycode);
	// indicates that 'button' was held down in the previous frame
	bool wasKeyDown(MouseButtons button);

	// gets the first key in the buffer that is currently held down by the user
	char GetAnyKeyDown(void) const;

	// setting true will cause input manager to ignore the mouse move 
	// event until it is set to false again
	void setIgnoreMouseMove(bool value) { ignoreMouseMove = value; }
	
	void BeginUpdate(void);
	void EndUpdate(void);

	void setMousePosition(Vector2 position);
	void setMousePosition(int x, int y);

	Vector2 getMousePosition(void) const { return Vector2((float)mouse_x, (float)mouse_y); }

	Delegate<key_event_handler>* getKeyBindings(NamedKey key) { return key_bindings[key]; }
	Delegate<mouse_event_handler>* getMouseBindings(NamedKey key) { return mouse_bindings[key]; }

	static void Init(HWND);

	static Input* GetInstance(void)
	{
		return instance;
	}

	static void Shutdown(void)
	{
		delete instance;
	}
};